//
//  SPModelBase.m
//  SPKit
//
//  Created by 李双鹏 on 15/10/04.
//  Copyright (c) 2015年 SP. All rights reserved.
//

#import "SPModelBase.h"
#import <objc/runtime.h>

#ifndef __BaseModel_m__
#define __BaseModel_m__ 0


@interface ModelObserver : NSObject
@property (nonatomic, weak) id<ModelObserverDelegate> delegate;
@property (nonatomic, strong) NSMutableSet *observedKeies;
+ (instancetype)observerWithDelegate:(id<ModelObserverDelegate>)delegate;
@end


@interface NSObject (ModelObserverDelegate)
/// 用来记录观察者的身份。
@property (nonnull, nonatomic, copy) NSString *modelObserverKey;
@end


@interface SPModelBase()
@property (nonatomic, strong) NSMutableDictionary *observers;
@end

@interface NSDictionary (_SPFilter)
- (NSDictionary *)_SP_filterNullValues;
@end
@interface NSArray (_SPFilter)
- (NSArray *)_SP_filterNullValues;
@end



///the base class
@implementation SPModelBase {
    BOOL _onObservingMyselfProperties;
}

NSString * __savedPath = @"";
+ (void)initialize{
    __savedPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
    __savedPath = [__savedPath stringByAppendingPathComponent:@"SPModels"];
    if (![[NSFileManager defaultManager] fileExistsAtPath:__savedPath]) {
        [[NSFileManager defaultManager] createDirectoryAtPath:__savedPath withIntermediateDirectories:YES attributes:nil error:nil];
    }
}

+ (instancetype)new {
    return [[self alloc] initWithDic:@{}];
}

- (NSMutableDictionary *)observers{
    if (!_observers)
    {
        _observers = [NSMutableDictionary dictionary];
    }
    return _observers;
}

- (NSMutableDictionary *)dic{
    if (!_dictionary) {
        _dictionary = [NSMutableDictionary dictionary];
    }
    return (id)_dictionary;
}

- (NSDictionary *)dictionary {
    if (!_dictionary) {
        _dictionary = [NSMutableDictionary dictionary];
    }
    return [_dictionary copy];
}

+ (instancetype)modelFromDic:(NSDictionary *)dic{
    // 为子类制定规则，供子类调用。
    // 请在子类中重写[initWithDic:]方法
    if (![dic isKindOfClass:[NSDictionary class]]) {
        return [[self alloc] initWithDic:@{}];
    }
    return [[self alloc] initWithDic:[dic _SP_filterNullValues]];
}

+ (instancetype)model {
    return [[self alloc] initWithDic:@{}];
}

- (instancetype)initWithDic:(NSDictionary *)dic{
    if (self = [super init]){
        // 本方法基类只负责功能维护，键值初始化请在子类中实现。
        _dictionary = [dic mutableCopy];
        _onObservingMyselfProperties = YES;
        
        unsigned int outCount, i;
        objc_property_t *properties = class_copyPropertyList([self class], &outCount);
        for (i = 0; i<outCount; i++)
        {
            objc_property_t property = properties[i];
            const char* char_f = property_getName(property);
            NSString *propertyName = [NSString stringWithUTF8String:char_f];
            if ([propertyName isEqualToString:@"dictionary"]) { continue; }
            
            [self addObserver:self forKeyPath:propertyName options:(NSKeyValueObservingOptionNew) context:nil];
        }
        free(properties);
        
    }
    return self;
}

- (instancetype)init {
    return [self initWithDic:@{}];
}

+ (NSArray *)modelsFromDicArray:(NSArray *)array{
    NSMutableArray *models = [NSMutableArray array];
        
    if ([array isKindOfClass:[NSArray class]]) {
        array = [array _SP_filterNullValues];
        [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            [models addObject:[self modelFromDic:obj]];        
        }];
    }
    
//    for (NSDictionary *dic in array) {
//    }
    return [NSArray arrayWithArray:models];
}


- (NSDictionary *)toDictionary{
    NSMutableDictionary *dic = self.dic;
    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList([self class], &outCount);
    for (i = 0; i<outCount; i++)
    {
        objc_property_t property = properties[i];
        const char* char_f = property_getName(property);
        NSString *propertyName = [NSString stringWithUTF8String:char_f];
        id propertyValue = [self valueForKey:(NSString *)propertyName];
        
        if ([propertyName isEqualToString:@"dictionary"]) { continue; }
        dic[propertyName] = propertyValue;
    }
    free(properties);
    return dic;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
    id value = [self valueForKeyPath:keyPath];
    if (value) {
        if ([value isKindOfClass:[SPModelBase class]]) {
            self.dic[keyPath] = [value dictionary];
        }else{
            self.dic[keyPath] = value;
        }
    }else{
        [self.dic removeObjectForKey:keyPath];
    }
}


- (void)dealloc{
    for (ModelObserver *observer in _observers.allValues)
    {
        for (NSString *property in observer.observedKeies) {
            [self removeObserver:observer forKeyPath:property context:nil];
            // NSLog(@"%@ RemoveObserver:%@->%@", self,observer,property);
        }
    }
    
    if (!_onObservingMyselfProperties) {
        return;
    }
    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList([self class], &outCount);
    for (i = 0; i<outCount; i++)
    {
        objc_property_t property = properties[i];
        const char* char_f = property_getName(property);
        NSString *propertyName = [NSString stringWithUTF8String:char_f];
        [self removeObserver:self forKeyPath:propertyName context:nil];
    }
    free(properties);
}
@end



@implementation SPModelBase (Persistence)


- (void)saveToDisk{
    [_dictionary writeToFile:[[self class] defaultPath] atomically:YES];
}
+ (instancetype)loadFromDisk{
    NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:[self defaultPath]];
    return [self modelFromDic:dic];
}

+ (void)clearInDisk {
    [[NSFileManager defaultManager] removeItemAtPath:[self defaultPath] error:nil];
}

+ (NSString *)defaultPath {
    return [__savedPath stringByAppendingFormat:@"/%@.dat", NSStringFromClass([self class])];
}

@end

@implementation SPModelBase (Obseration)


- (void)observedBy:(NSObject<ModelObserverDelegate> *)obj atProperty:(NSString *)property{
    NSString *key = obj.modelObserverKey;
    
    ModelObserver *observer = self.observers[key];
    if (!observer) {
        observer = [ModelObserver observerWithDelegate:obj];
        self.observers[key] = observer;
    }
    if (![observer.observedKeies containsObject:property])
    {
        [observer.observedKeies addObject:property];
        [self addObserver:observer forKeyPath:property options:NSKeyValueObservingOptionNew context:nil];
    }
}
- (void)removeObservedBy:(NSObject<ModelObserverDelegate> *)obj {
    NSString *key = obj.modelObserverKey;
    
    ModelObserver *observer = self.observers[key];
    if (!observer) {
        return;
    }
    for (NSString *property in observer.observedKeies) {
        [self removeObserver:observer forKeyPath:property context:nil];
    }
    [self.observers removeObjectForKey:key];
}
- (void)removeObservedBy:(NSObject<ModelObserverDelegate> *)obj atProperty:(NSString *)property {
    NSString *key = obj.modelObserverKey;
    
    ModelObserver *observer = self.observers[key];
    if (!observer) {
        return;
    }
    [self removeObserver:observer forKeyPath:property context:nil];
    [observer.observedKeies removeObject:property];
    if (!observer.observedKeies.count)
    {
        [self.observers removeObjectForKey:key];
    }
}

@end


@implementation ModelObserver

+ (instancetype)observerWithDelegate:(id<ModelObserverDelegate>)delegate{
    return [[self alloc] initWithDelegate:delegate];
}
- (instancetype)initWithDelegate:(id<ModelObserverDelegate>)delegate
{
    self = [super init];
    if (self) {
        _delegate = delegate;
    }
    return self;
}

- (NSMutableSet *)observedKeies{
    if (!_observedKeies)
    {
        _observedKeies = [NSMutableSet set];
    }
    return _observedKeies;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
//    if ([self.delegate respondsToSelector:@selector(observedObject:changeAtProperty:)])
//    {
//    }
    [self.delegate observedObject:object changeAtProperty:keyPath];
}

- (void)dealloc{
    // NSLog(@"Dealloc:%@\n=================\n", self);
}

@end


@implementation NSObject (ModelObserverDelegate)

- (void)setModelObserverKey:(NSString *)modelObserverKey {
    objc_setAssociatedObject(self, "setModelObserverKey:", modelObserverKey, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)modelObserverKey {
    NSString *key = objc_getAssociatedObject(self, "setModelObserverKey:");
    if (!key) {
        key = [NSString stringWithFormat:@"%p", self];
        self.modelObserverKey = key;
    }
    return key;
}

@end


@implementation NSDictionary (_SPFilter)

- (NSDictionary *)_SP_filterNullValues {
    NSMutableDictionary *dic = [NSMutableDictionary dictionary];
    NSNull *null = [NSNull null];
    
    [self enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        if (obj == null ||
            [obj isKindOfClass:[NSNull class]]) {
            return ;
        }
        dic[key] = obj;
    }];
    
    return [NSDictionary dictionaryWithDictionary:dic];
}

@end

@implementation NSArray (_SPFilter)

- (NSArray *)_SP_filterNullValues {
    NSPredicate *pdc = [NSPredicate predicateWithBlock:^BOOL(id  _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
        return evaluatedObject != [NSNull null];
    }];
    return [self filteredArrayUsingPredicate:pdc];
}

@end


//NSString *notNull(NSString *str){
//    if (!str || [str isKindOfClass:[NSNull class]]) {
//        return @"";
//    }
//    return str;
//}
#endif


